package io.gitee.devaper.starlight.module.room.websocket

import io.gitee.devaper.starlight.common.entity.po.*
import io.gitee.devaper.starlight.module.room.websocket.event.server.*
import io.gitee.devaper.starlight.security.*
import org.springframework.security.core.*
import org.springframework.web.socket.*
import java.util.concurrent.locks.*
import kotlin.concurrent.*
import kotlin.contracts.*
import kotlin.math.*


/**
 * 视频信息
 */
class Video {

    /**
     * 正在播放的URL
     */
    private var url: String? = null

    /**
     * 视频状态
     */
    private var state: VideoState = VideoState.STOP

    /**
     * 视频倍速
     */
    private var speed: Double = 1.0

    /**
     * 最后计算的视频播放进度
     */
    private var lastComputedProgress: Long = 0

    /**
     * 最后在服务器进行计算的时间, 即最后一次计算lastComputedStep是在什么时候
     */
    private var lastComputeTimeMillis: Long = 0

    fun changeVideo(url: String) {
        reset()
        this.url = url
    }

    fun currentUrl() = url

    fun updateState(state: VideoState) {
        if (state == this.state) {
            return
        }
        compute()
        this.state = state
    }

    fun currentState() = state

    fun updateSpeed(speed: Double) {
        if (this.speed == speed) {
            return
        }
        compute()
        this.speed = speed
    }

    fun currentSpeed() = speed

    fun updateTime(time: Double) {
        lastComputedProgress = floor(time).toLong()
        lastComputeTimeMillis = System.currentTimeMillis()
    }

    fun currentTime(): Long {
        compute()
        return this.lastComputedProgress
    }

    private fun reset() {
        url = null
        state = VideoState.STOP
        speed = 1.0
        lastComputedProgress = 0
        lastComputeTimeMillis = 0
    }

    private fun compute() {
        val currentTimeMillis = System.currentTimeMillis()
        when (state) {
            VideoState.PLAY -> {
                val interval = currentTimeMillis - lastComputeTimeMillis
                lastComputedProgress += floor(interval * speed).toLong()
                lastComputeTimeMillis = currentTimeMillis
            }

            VideoState.PAUSE -> {
                lastComputeTimeMillis = currentTimeMillis
            }

            VideoState.STOP -> {
                lastComputeTimeMillis = currentTimeMillis
            }
        }

    }

}

/**
 * 房间信息
 *
 * @param id 房间ID
 * @param creator 创建者
 * @param name 名称
 * @param password 密码
 * @param capacity 容量
 * @param clients 客户端列表
 * @param lock 房间的读写锁
 * @param video 视频信息
 */
class Room(
    val id: String,
    var creator: UserPO,
    var name: String,
    var password: String? = null,
    var capacity: Int,
    val clients: MutableCollection<WebSocketSession> = HashSet(),
    val lock: ReadWriteLock = ReentrantReadWriteLock(true),
    val video: Video = Video()
) {

    /**
     * 获取房间的所有用户信息
     */
    val users: Collection<UserPO>
        get() = clients.map { ((it.principal as Authentication).principal as DefaultUserDetails).user }

    /**
     * 广播消息到房间所有的客户端
     *
     * @param message 消息对象
     * @param ignore 忽略的会话, 即消息不希望发送给的客户端
     * @param useLock 是否使用锁, 如果是读写锁那么采用读锁
     */
    fun broadcast(message: WebSocketMessage<*>, ignore: WebSocketSession? = null, useLock: Boolean = false) {
        if (useLock) {
            lock.readLock().withLock {
                broadcast0(message, ignore)
            }
        } else {
            broadcast0(message, ignore)
        }
    }

    private fun broadcast0(message: WebSocketMessage<*>, ignore: WebSocketSession?) {
        clients.forEach { client ->
            if (client.isOpen && client != ignore) {
                client.sendMessage(message)
            }
        }
    }

    override fun hashCode(): Int {
        return id.hashCode()
    }

    override fun equals(other: Any?): Boolean {
        return other is Room && id == other.id
    }


    @OptIn(ExperimentalContracts::class)
    inline fun read(block: (Room) -> Unit) {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        lock.readLock().withLock {
            block(this)
        }
    }

    @OptIn(ExperimentalContracts::class)
    inline fun write(block: (Room) -> Unit) {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        lock.writeLock().withLock {
            block(this)
        }
    }

}